package com.note.notes.controller;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.note.common.domain.File;
import com.note.common.dto.BucketPolicyConfigDto;
import com.note.common.dto.MinioUploadDto;
import com.note.common.enums.LogEnum;
import com.note.common.interfaces.SystemLog;
import com.note.common.utils.FileDigest;
import com.note.common.utils.FileUtils;
import com.note.common.utils.TimeUtils;
import com.note.common.vo.ResponseVo;
import com.note.config.config.CustomBlockHandler;
import com.note.config.constants.GatewayConstants;
import com.note.notes.service.NoteService;
import com.note.notes.service.internal.MinioFileService;
import io.minio.*;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.constraints.NotBlank;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.Locale;

/**
 * @Package：com.note.notes.controller
 * @Name：MinioController
 * @Author：热伊木
 * @Email：uyevan@163.com
 * @Date：2024-04-30-16:29
 * @Description：Minio文件上传控制层
 */

@Slf4j
@Validated
@RestController
@RequestMapping("/minio")
@Tag(name = "MinioController", description = "Minio文件上传")
public class MinioController {
    /*Minio源*/
    @Value("${minio.endpoint}")
    private String ENDPOINT;

    @Value("${minio.bucketName}")
    private String BUCKET_NAME;

    @Value("${minio.accessKey}")
    private String ACCESS_KEY;

    @Value("${minio.secretKey}")
    private String SECRET_KEY;

    @Value("${minio.file.extensions}")
    private String[] fileExtensions;

    @Value("${minio.oss-url}")
    private String OSS_URL;

    @Autowired
    private MinioFileService minioFileService;

    @Autowired
    private NoteService noteService;

    @PostMapping("/upload")
    @SystemLog(type = LogEnum.UPLOAD_LOG)
    @Operation(summary = "upload", description = "文件上传")
    @SentinelResource(value = "upload",
            blockHandler = "blockHandleException",
            blockHandlerClass = CustomBlockHandler.class,
            fallbackClass = CustomBlockHandler.class,
            fallback = "fallBackHandle")
    @Parameters({
            @Parameter(name = "username", description = "用户名", required = true, in = ParameterIn.QUERY),
            @Parameter(name = "noteId", description = "绑定笔记Id", required = true, in = ParameterIn.QUERY),
            @Parameter(name = "file", description = "上传的文件", required = true, in = ParameterIn.QUERY)
    })
    public ResponseVo upload(
            @NotBlank(message = "用户名不能为空") @RequestParam("username") String username,
            @NotBlank(message = "笔记Id不能为空") @RequestParam("noteId") String noteId,
            @RequestPart("file") MultipartFile file) {

        try {
            //TODO 验证文件大小
            long size = file.getSize();
            if (size == 0L) {
                return ResponseVo.fail("禁止上传空文件,稍后重试");
            }
            //TODO 验证无后缀文件
            String filename = file.getOriginalFilename();
            int index = filename.lastIndexOf(".");
            if (index == -1) {
                return ResponseVo.fail("禁止上传无后缀文件,稍后重试");
            }
            //TODO 验证文件后缀
            String etc = filename.substring(index + 1);//文件后缀
            if (!Arrays.asList(fileExtensions).contains(etc.toLowerCase(Locale.ROOT))) {
                return ResponseVo.fail("禁止上传该类型文件,稍后重试");
            }


            //TODO 笔记状态 --- 暂不验证
            /*if (!noteService.isNoteExits(noteId)) {
                //笔记不存在
                return ResponseVo.fail("笔记不存在,稍后重试");
            }*/


            //TODO 设置对象名称
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd", Locale.CHINA);
            //存储文件名
            String objectName = String.format("%s/%s-%s.%s", simpleDateFormat.format(new Date()),
                    IdUtil.simpleUUID(), Base64.encode(username), etc);

            //TODO 链接客户端
            MinioClient minioClient = MinioClient.builder()
                    .endpoint(ENDPOINT)
                    .credentials(ACCESS_KEY, SECRET_KEY)
                    .build();
            //TODO 存储桶状态
            boolean bucketState = minioClient.bucketExists(BucketExistsArgs.builder().bucket(BUCKET_NAME).build());
            if (bucketState) {
                log.info("存储桶已被创建过:{}", minioClient.listBuckets());
            } else {
                //创建存储桶并设置只读权限
                minioClient.makeBucket(MakeBucketArgs.builder().bucket(BUCKET_NAME).build());
                BucketPolicyConfigDto policyConfigDto = createBucketPolicyConfigDto(BUCKET_NAME);
                SetBucketPolicyArgs setBucketPolicyArgs = SetBucketPolicyArgs.builder()
                        .bucket(BUCKET_NAME)
                        .config(JSONUtil.toJsonStr(policyConfigDto))
                        .build();
                minioClient.setBucketPolicy(setBucketPolicyArgs);
            }
            //TODO 上传文件
            PutObjectArgs putObjectArgss = PutObjectArgs.builder()
                    .bucket(BUCKET_NAME).
                    object(objectName).
                    contentType(file.getContentType())
                    .stream(file.getInputStream(), size, (long) ObjectWriteArgs.MIN_MULTIPART_SIZE)
                    .build();
            minioClient.putObject(putObjectArgss);
            //TODO 响应信息（有误会报错）
            log.info("上传文件成功:{}", putObjectArgss);
            MinioUploadDto minioUploadDto = new MinioUploadDto();
            minioUploadDto.setName(objectName);
            minioUploadDto.setUrl(String.format("%s/%s/%s", OSS_URL, BUCKET_NAME, objectName));
            minioUploadDto.setFileMd5(FileDigest.getFileMD5(FileUtils.transferToFile(file)));

            //TODO 调用RPC记录
            File recordFile = new File();
            recordFile.setUsername(username);
            recordFile.setFileName(minioUploadDto.getName());
            recordFile.setNoteId(Base64.encode(username)/*noteId*/);
            recordFile.setUrl(minioUploadDto.getUrl());
            recordFile.setCreateTime(TimeUtils.getCurrentDate());
            recordFile.setFileMd5("");
            minioFileService.createFile(GatewayConstants.INTERNAL_KEY, recordFile);
            return ResponseVo.success(minioUploadDto);
        } catch (Exception e) {
            throw new RuntimeException("上传错误,稍后重试");
        }
    }

    /**
     * 存储桶策略 --- 只读
     *
     * @param bucketName
     * @return
     */
    private BucketPolicyConfigDto createBucketPolicyConfigDto(String bucketName) {
        BucketPolicyConfigDto.Statement statement = BucketPolicyConfigDto.Statement.builder()
                .Effect("Allow")
                .Principal("*")
                .Action("s3:GetObject")
                .Resource("arn:aws:s3:::" + bucketName + "/*.**").build();
        return BucketPolicyConfigDto.builder()
                .version("2024-05-01")
                .statements(CollUtil.toList(statement))
                .build();
    }
}
